Java连接西门子PLC(S7 您所在的位置:网站首页 西门子plc s1500 Java连接西门子PLC(S7

Java连接西门子PLC(S7

2024-03-27 23:16| 来源: 网络整理| 查看: 265

1 ,项目基于springboot,maven引入:

com.github.s7connector s7connector 2.1

2,自定义PLC配置,配置包括多个PLC,每个PLC多DB块(后面设计每个DB块对应一个实体类,用于数据读写),每个DB块包含多种类型数据(每个数据类型对应实体类的成员变量)

package service.connect.config; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; import java.util.List; /** * 通过TCP对接plc设备配置 */ @Configuration @ConfigurationProperties(prefix = "spring.plc") public class PLCSetting { /** * PLC设备列表 */ private List devices; public List getDevices() { return devices; } public void setDevices(List devices) { this.devices = devices; } public static class Device { /** * PLC设备IP地址 */ private String ip; /** * PLC设备端口 */ private Integer port; /** * 连接超时,单位:毫秒 * 默认5000ms */ private Integer connectTimeout = 5000; /** * 默认0 */ private Integer rack = 0; /** * 默认1 */ private Integer slot = 1; /** * 一个设备可以包含多个数据块 */ List dataBlock; public String getIp() { return ip; } public void setIp(String ip) { this.ip = ip; } public Integer getPort() { return port; } public void setPort(Integer port) { this.port = port; } public Integer getConnectTimeout() { return connectTimeout; } public void setConnectTimeout(Integer connectTimeout) { this.connectTimeout = connectTimeout; } public Integer getRack() { return rack; } public void setRack(Integer rack) { this.rack = rack; } public Integer getSlot() { return slot; } public void setSlot(Integer slot) { this.slot = slot; } public List getDataBlock() { return dataBlock; } public void setDataBlock(List dataBlock) { this.dataBlock = dataBlock; } } public static class DataBlock { /** * PLC数据块编号 */ private Integer databaseNumber; /** * 偏移量 默认为0 */ private Integer offset = 0; /** * 是否轮询、重复读取数据,默认:true */ private boolean readRepeat = true; /** * 数据读取间隔,单位毫秒,默认100ms */ private Integer readRepeatInterval = 100; /** * 数据对应的实体类对象全路径,数据实体配置数据地址偏移量、长度等相关信息 */ private String entityClassName; public Integer getDatabaseNumber() { return databaseNumber; } public void setDatabaseNumber(Integer databaseNumber) { this.databaseNumber = databaseNumber; } public Integer getOffset() { return offset; } public void setOffset(Integer offset) { this.offset = offset; } public boolean isReadRepeat() { return readRepeat; } public void setReadRepeat(boolean readRepeat) { this.readRepeat = readRepeat; } public Integer getReadRepeatInterval() { return readRepeatInterval; } public void setReadRepeatInterval(Integer readRepeatInterval) { this.readRepeatInterval = readRepeatInterval; } public String getEntityClassName() { return entityClassName; } public void setEntityClassName(String entityClassName) { this.entityClassName = entityClassName; } } }

3,yml配置示例(配置项参考上面的配置类):这里使用两种实体类(ExampleBlock1、ExampleBlock2)写法读取同一个DB块(database-number: 2)的数据

spring: plc: devices: - ip: 192.168.0.1 port: 102 slot: 1 rack: 0 dataBlock: - database-number: 2 read-repeat: true read-repeat-interval: 1000 offset: 0 entity-class-name: service.connect.entity.plc.ExampleBlock1 - database-number: 2 read-repeat: true read-repeat-interval: 1000 offset: 0 entity-class-name: service.connect.entity.plc.ExampleBlock2

4,连接与操作,类比较长,此处做拆分备注。

    4.1,项目启动时初始化连接:注入配置,定义配置缓存Map(用于后续执行读写操作),定义线程池(按需而定,我这里需要定时读取数据)

@Resource private PLCSetting plcSetting; Map dataBlockInfoMap = new HashMap(); private ScheduledExecutorService executorService = Executors.newScheduledThreadPool(16); @PostConstruct public void init() { if (!CollectionUtils.isEmpty(plcSetting.getDevices())) { plcSetting.getDevices().forEach(plc -> { S7Connector s7Connector = S7ConnectorFactory .buildTCPConnector() .withHost(plc.getIp()) .withPort(plc.getPort()) .withTimeout(plc.getConnectTimeout()) //连接超时时间 .withRack(plc.getRack()) .withSlot(plc.getSlot()) .build(); S7Serializer s7Serializer = S7SerializerFactory.buildSerializer(s7Connector); if (!CollectionUtils.isEmpty(plc.getDataBlock())) { plc.getDataBlock().forEach(dataBlock -> { //开启轮询读取数据 if (dataBlock.isReadRepeat()) { try { Class clazz = getClass().getClassLoader().loadClass(dataBlock.getEntityClassName()); executorService.scheduleWithFixedDelay(() -> { try { //读取数据 Object data = s7Serializer.dispense(clazz, dataBlock.getDatabaseNumber(), dataBlock.getOffset()); //TODO data if (logger.isWarnEnabled()) { logger.info("{}: [{}]", data.getClass(), data); } } catch (Exception e) { e.printStackTrace(); } }, 1000, dataBlock.getReadRepeatInterval(), TimeUnit.MILLISECONDS); } catch (ClassNotFoundException e) { e.printStackTrace(); } } //缓存连接信息 DataBlockInfo dataBlockInfo = new DataBlockInfo(); dataBlockInfo.setS7Connector(s7Connector); dataBlockInfo.setS7Serializer(s7Serializer); dataBlockInfo.setDataBlock(dataBlock); dataBlockInfoMap.put(dataBlock.getEntityClassName(), dataBlockInfo); }); } }); } }

数据块缓存用的内部类:

public static class DataBlockInfo { private PLCSetting.DataBlock dataBlock; private S7Serializer s7Serializer; private S7Connector s7Connector; public PLCSetting.DataBlock getDataBlock() { return dataBlock; } public void setDataBlock(PLCSetting.DataBlock dataBlock) { this.dataBlock = dataBlock; } public S7Serializer getS7Serializer() { return s7Serializer; } public void setS7Serializer(S7Serializer s7Serializer) { this.s7Serializer = s7Serializer; } public S7Connector getS7Connector() { return s7Connector; } public void setS7Connector(S7Connector s7Connector) { this.s7Connector = s7Connector; } }

    4.2,数据写入

        4.2.1,s7connector自带的store方法:通过DB块配置的number、offset以及实体类来保存数据,但是这个方法会刷新实体类所有成员属性对应的数据,如果属性没有赋值,也会覆盖原有的数据。

/** * 保存PLC数据,整体保存,保存所有数据 */ public void save(T t) { if (dataBlockInfoMap.containsKey(t.getClass().getName())) { DataBlockInfo dataBlockInfo = dataBlockInfoMap.get(t.getClass().getName()); dataBlockInfo.getS7Serializer().store(t, dataBlockInfo.getDataBlock().getDatabaseNumber(), dataBlockInfo.getDataBlock().getOffset()); } else { logger.error("[{}] 对应的数据块未配置,或者读取PLC配置失败!", t.getClass().getName()); } }

        4.2.2,我自己的需求是当属性为null时不做处理:注意:parseBytes方法中关于值为String类型的处理参考

com.github.s7connector.impl.serializer.converter.StringConverter的insert方法,如有其它需求,可以参考该包下的其它Converter /** * 保存PLC数据,单属性保存,保存所有不为null的属性 */ public void saveBySingle(T t) { if (dataBlockInfoMap.containsKey(t.getClass().getName())) { try { DataBlockInfo dataBlockInfo = dataBlockInfoMap.get(t.getClass().getName()); Arrays.stream(t.getClass().getFields()).forEach(field -> { try { if (!ObjectUtils.isEmpty(field.get(t))) { S7Variable s7Variable = field.getAnnotation(S7Variable.class); if (!ObjectUtils.isEmpty(s7Variable)) { byte[] bytes = parseBytes(field.get(t), s7Variable); dataBlockInfo.getS7Connector().write(DaveArea.DB, dataBlockInfo.getDataBlock().getDatabaseNumber(), s7Variable.byteOffset(), bytes); } else { logger.error("类[{}]的属性[{}]需要添加注解标签", t.getClass().getName(), field.getName()); } } } catch (IllegalAccessException e) { logger.error("保存PLC数据失败", e); } }); } catch (Exception e) { e.printStackTrace(); } } else { logger.error("[{}] 对应的数据块未配置,或者读取PLC配置失败!", t.getClass().getName()); } } private byte[] parseBytes(Object value, S7Variable s7Variable) { byte[] bytes = null; if (value instanceof Integer) { bytes = new byte[2]; intToBytes(((Integer) value).intValue(), bytes); } else if (value instanceof byte[]) { bytes = (byte[]) value; } else if (value instanceof String) { byte[] buffer = ((String) value).getBytes(); bytes = new byte[buffer.length + 2]; bytes[0] = (byte) s7Variable.size(); bytes[1] = (byte) ((String) value).length(); for (int i = 0; i < buffer.length; ++i) { bytes[i + 2] = (byte) (buffer[i] & 255); } } return bytes; } /** * PLC中word类型占用2个byte */ private void intToBytes(int number, byte[] outBytes) { outBytes[0] = (byte) (number >> 8); outBytes[1] = (byte) number; }

        4.2.3,DB块对应的实体类:偏移量不能写错

public class ExampleBlock1 implements Serializable { @S7Variable(byteOffset = 0, arraySize = 16, type = S7Type.WORD) public Integer[] integer; @S7Variable(byteOffset = 32, size = 50, type = S7Type.STRING) public String string0; //toString... } public class ExampleBlock2 implements Serializable { @S7Variable(byteOffset = 0, size = 2, type = S7Type.WORD) public Integer integer0; @S7Variable(byteOffset = 2, size = 2, type = S7Type.WORD) public Integer integer1; // 中间还有很多。。。太长就省略了 @S7Variable(byteOffset = 28, size = 2, type = S7Type.WORD) public Integer integer14; @S7Variable(byteOffset = 30, size = 2, type = S7Type.WORD) public Integer integer15; @S7Variable(byteOffset = 32, size = 50, type = S7Type.STRING) public String string0; //toString... }

最后来博图的截图

 数据读取执行结果:

2022-01-19 16:47:35.256 [pool-6-thread-2] INFO  c.d.p.s.connect.service.PLCConnect - class city.dekun.park.service.connect.entity.plc.ExampleBlock2: [integer0[0], integer1[18], integer2[255], integer3[65535], integer4[0], integer5[0], integer6[0], integer7[0], integer8[0], integer9[0], integer10[0], integer11[0], integer12[0], integer13[0], integer14[0], integer15[18], string0[asdfasfd] ] 2022-01-19 16:47:35.287 [pool-6-thread-10] INFO  c.d.p.s.connect.service.PLCConnect - class city.dekun.park.service.connect.entity.plc.ExampleBlock1: [integer[[Ljava.lang.Integer;@11f3bbfe], string0[asdfasfd] ]



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有